#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#define MODE S_IRUSR| S_IWUSR| S_IRGRP| S_IROTH

void usage() {
    printf("usage:cp src dest\n");
}

int copy_file(const char *dest_buf, const char *src_buf, mode_t mode) {
    int n;
    char buf[256];
    int fd_src, fd_dest;

    if ((fd_src = open(src_buf, O_RDONLY)) < 0)	{
        fprintf(stderr, "open:%s\n", strerror(errno));	
        exit(0);
    }

    if ((fd_dest = open(dest_buf, O_RDWR | O_CREAT | O_TRUNC, mode)) < 0) {
        fprintf(stderr, "open:%s\n", strerror(errno));	
        exit(0);
    }
    while ((n = read(fd_src, buf, sizeof(buf))) ) {
        if (n < 0 && errno == EINTR)
            continue;
        if (n < 0 && errno != EINTR ) {
            fprintf(stderr, "read:%s\n", strerror(errno));	
            return -1;
        }
        if (write(fd_dest, buf, strlen(buf)) < 0) {
            fprintf(stderr, "write:%s\n", strerror(errno));	
            return -1;
        }	
    }
    close(fd_src);
    close(fd_dest);
    return 0;
}

int copy_dir(char *dest_dir, char *src_buf, mode_t mode) {
    // if is a dir, then create dir and travel dir.
    struct dirent *pdir;
    struct stat statbuf;
    char *psrc, *pdest;

    if (src_buf == NULL || dest_dir == NULL)
        return -1;

    DIR *dp = opendir(src_buf);
    if (dp == NULL) {
        fprintf(stderr, "readdir:%s\n", strerror(errno)); 
        return -1;
    }
    
    char *temp = strrchr(src_buf, '/');
    // make dir
    strncat(dest_dir, temp, strlen(temp));
    //printf("dest_dir:%s\n", dest_dir);
    if (mkdir(dest_dir, mode) < 0) {
        fprintf(stderr, "mkdir:%s\n", strerror(errno)); 
        return -1;
    }

    strcat(src_buf, "/");
    strcat(dest_dir, "/");
    psrc = src_buf + strlen(src_buf);
    pdest = dest_dir + strlen(dest_dir);
    // travel dir to create files and dirs
    while ((pdir = readdir(dp)) != NULL) {
        if (strcmp(pdir->d_name, ".") == 0 ||
                strcmp(pdir->d_name, "..") == 0) {
            continue; 
        }
        strcat(src_buf, pdir->d_name);
        //printf("src_buf:%s\n", src_buf);
        if (lstat(src_buf, &statbuf) < 0) {
            fprintf(stderr, "lstat in cpdir:%s\n", strerror(errno)); 
            return -1;
        }
        if (S_ISREG(statbuf.st_mode)) {
            strcat(dest_dir, pdir->d_name);
            if (copy_file(dest_dir, src_buf, statbuf.st_mode) < 0) {
                return -1; 
            }
        }
        if (S_ISDIR(statbuf.st_mode)) {
            if (copy_dir(dest_dir, src_buf, statbuf.st_mode) < 0) {
                return -1; 
            } 
        }
        *psrc = *pdest = '\0';
        //printf("src_buf:%s\n", src_buf);
    }
}

int main(int argc, char *argv[]) {
    char buf[256];
    char dir_buf[256];
    char src_buf[256];
    char dest_buf[256];
    struct stat stat_buf;
    struct dirent *pdir;

    if (argc != 3) {
        usage();
        exit(0);
    }

    if (argv[1][0] != '/') {
        if (getcwd(dir_buf, sizeof(dir_buf)) < 0) {
            fprintf(stderr, "getcwd:%s\n", strerror(errno));	
            exit(0);	
        }
        strcat(dir_buf, "/");
        strncat(dir_buf, argv[1], strlen(argv[1]));
        strcpy(src_buf, dir_buf);
    }
    else {
        strcpy(src_buf, argv[1]); 
    }

    memset(dir_buf, '\0', sizeof(dir_buf));
    // relative path.
    if (argv[2][0] != '/') {
        if (getcwd(dir_buf, sizeof(dir_buf)) < 0) {
            fprintf(stderr,"getcwd:%s\n", strerror(errno));	
            exit(0);	
        }
        strcat(dir_buf, "/");
        strncat(dir_buf, argv[2], strlen(argv[2]));
        strcpy(dest_buf, dir_buf);
    }
    else { //absolute path
        strcpy(dest_buf, dir_buf); 
    }

    if (lstat(src_buf, &stat_buf) < 0)	{
        fprintf(stderr, "lstat in main:%s\n", strerror(errno));	
        exit(0);
    }

    if (S_ISREG(stat_buf.st_mode)) {
        if (copy_file(dest_buf, src_buf, stat_buf.st_mode) < 0) {
            fprintf(stderr, "copy_file:%s\n", strerror(errno));
            exit(0);
        }
    }

    if (S_ISDIR(stat_buf.st_mode)) {
        // dial with the last slash to make sure 
        // no slash in the last place.
        char *p1 = src_buf + strlen(src_buf); 
        if (*(--p1) == '/') {
            *p1 = '\0';
        }
        p1 = dest_buf + strlen(dest_buf); 
        if (*(--p1) == '/') {
            *p1 = '\0';
        }
        if (copy_dir(dest_buf, src_buf, stat_buf.st_mode) < 0) {
            fprintf(stderr, "copy_dir:%s\n", strerror(errno)); 
            exit(0);
        }
    }

    return 0;
}
